import { Body, Controller, Get, Param, Post, Req } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AddChargeOrder } from 'src/entities/AddChargeOrder';
import { ChargeOrder } from 'src/entities/ChargeOrder';
import { UserWallet } from 'src/entities/UserWallet';
import { UserWalletLog } from 'src/entities/UserWalletLog';
import { MqttService } from 'src/mqtt/mqtt.service';
import { RabbitmqService } from 'src/rabbitmq/rabbitmq.service';
import { RedisService } from 'src/redis/redis.service';
import { createOrderNo, errorReturn, getTime, isNumber, successReturn } from 'src/utils';
import { ChargeOrderService } from '../charge-order/charge-order.service';
import { ChargeStrategyService } from '../charge-strategy/charge-strategy.service';
import { DeviceService } from '../device/device.service';
import { MerchantService } from '../merchant/merchant.service';
import { ChargeService } from './charge.service';

@Controller('charge')
export class ChargeController {
    private starChargeResKey = '';
    private addChargeResKey = '';
    private stopChargeResKey = '';

    private chargeTopics = '';

    private chargeResExchange = '';
    private chargeResQueue = '';

    constructor(
        private chargeService:ChargeService,
        private redis:RedisService,
        private deviceService:DeviceService,
        private chargeOrderService:ChargeOrderService,
        private chargeStrategyService:ChargeStrategyService,
        private merchantService:MerchantService,
        private mqttService:MqttService,
        private rabbitmqService:RabbitmqService,
        private configService:ConfigService
    ) {
        const chargeKeys = this.configService.get('redisKey.charge')
        this.starChargeResKey = chargeKeys.startCharge
        this.addChargeResKey =  chargeKeys.addCharge
        this.stopChargeResKey =  chargeKeys.stopCharge

        this.chargeTopics = this.configService.get('mqtt.topic.charge')

        this.chargeResExchange = this.configService.get('rabbitmq.exchange.chargeResExchange')
        this.chargeResQueue = this.configService.get('rabbitmq.queue.chargeResQueue')
        
    }

    @Post()
    async charge(@Body() dto:{deviceId:number, portNo:number, amount:number}, @Req() {userId, clientSource, IS_WECHANT}) {
        const deviceId = Number(dto.deviceId)
        const portNo = Number(dto.portNo)
        const amount = Number(dto.amount)
        if(!isNumber(deviceId) || !isNumber(portNo) || !isNumber(amount)) return null
        if(deviceId < 1 || portNo < 1 || amount < 0) return null

        const lockKey = `charge:${deviceId}:${portNo}`
        const lock = await this.redis.store.set(lockKey,'', {NX : true, EX : 20})
        if(!lock) return errorReturn('插座已被占用，请选择其它插座')

        const device = await this.deviceService.device(deviceId, ['strategyId', 'simCardNo', 'merchantId', 'subMerchantId'])

        if(!device || !device.strategyId || !device.simCardNo) {
            this.redis.del(lockKey)
            return null
        }

        const strategy = await this.chargeStrategyService.strategy(device.strategyId)
        if(!strategy) {
            this.redis.del(lockKey)
            return null
        }

        const time = getTime()
        const orderNo = createOrderNo(userId)

        let order:{[key in keyof ChargeOrder]? : ChargeOrder[key]} = {
            userId : userId,
            orderNo : orderNo,
            deviceId : deviceId,
            portNo : portNo,
            feeAmount : '0',
            feeRewardAmount : '0',
            chargeAmount : String(amount),
            clientSource : IS_WECHANT ? 1 : 2,
            strategyRules : strategy.rules,
            strategyType : strategy.strategyType,
            unitPrice : strategy.unitPrice,
            settleUnit : strategy.settleUnit,
            status : 1,
            createTime : time
        }

        if(device.merchantId) {
            order.merchantId = device.merchantId
        }

        if(device.subMerchantId) {
            const subMerchant = await this.merchantService.merchant(device.subMerchantId, ['profitRate'])
            if(subMerchant) {
                order.subMerchantId = device.subMerchantId
                order.profitRate = subMerchant.profitRate
            }
        }

        let isSuccess = false

        const queryRunner = await this.chargeService.startTransaction()


        try {
            let wallet = await queryRunner.manager.findOne<UserWallet>(UserWallet, {
                where : {userId : order.userId},
                select : ['amount', 'rewardAmount'],
                lock : {
                    mode : 'pessimistic_read'
                }
            })

            let rechargeBalance = Number(wallet.amount) //充值的余额
            let rewardBalance = Number(wallet.rewardAmount) //赠送的余额

            if((rechargeBalance + rewardBalance) < amount) { //余额不足
                return errorReturn('余额不足')
            }

            let { walletLog, walletUpdate} = this.chargeService.walletUpdate(amount, rechargeBalance, rewardBalance, order, wallet)
            walletLog.type = 2
            

            {
                //更新钱包
                const { affected } = await queryRunner.manager.update<UserWallet>(UserWallet, { userId : order.userId }, walletUpdate)
                if(affected < 1) {
                    return errorReturn('更新钱包失败')
                }
            }

            let orderId = 0

            {
                //加入订单
                const { raw } = await queryRunner.manager.insert<ChargeOrder>(ChargeOrder, order)
                if(!raw || raw.affectedRows < 1) {
                    return errorReturn('加入定点失败')
                }
                walletLog.relationId = orderId = raw.insertId
            }

            {
                //加入钱包明细
                walletLog.createTime = order.createTime
                const { raw } = await queryRunner.manager.insert<UserWalletLog>(UserWalletLog, walletLog)
                if(!raw || raw.affectedRows < 1) {
                    return errorReturn('加入钱包明细失败')
                }
            }

            //const chargeParams = {portNo : order.portNo, orderId : orderId, strategyType : order.strategyType, amount}
            
            this.redis.store.set(this.starChargeResKey + orderId, String(userId), {NX : true, EX : 70})
            this.rabbitmqService.publish(this.chargeResExchange, this.chargeResQueue, {type : 1, orderId}, {expiration : 60000, timestamp : time})
            this.mqttService.publish(this.chargeTopics + '/' + device.simCardNo, {
                cmdType : 28,
                debug : 0,
                chargeAction : 0,
                outletNum:order.portNo,
                orderMsgId : orderId,
                consumeMode:order.strategyType,
                payMoney : amount * 100,
                totalTimOrVol : order.strategyType == 2 ? amount * Number(order.unitPrice) : amount * Number(order.unitPrice) * 60,
                unlockPwd : '',
            })

            
            await queryRunner.commitTransaction()

            isSuccess = true

            return successReturn(orderId)
            

        } catch (error) {
            console.log(error)
        } finally {
            if(!isSuccess) {
                await queryRunner.rollbackTransaction()
            }
            await queryRunner.release()

            this.redis.del(lockKey)
        }

        return errorReturn()
    }

    @Post('addCharge')
    async addCharge(@Body() dto:{chargeOrderId:number, amount:number}, @Req() {userId, clientSource, IS_WECHANT}) {
        const chargeOrderId = Number(dto.chargeOrderId)
        const amount = Number(dto.amount)
        if(!isNumber(chargeOrderId) || !isNumber(amount)) return null
        if(chargeOrderId < 1 || amount < 0) return null

        const chargeOrder = await this.chargeOrderService.order(chargeOrderId, ['deviceId', 'portNo', 'strategyType', 'status', 'unitPrice', 'settleUnit'])
        if(!chargeOrder || chargeOrder.status != 1) return false

        const device = await this.deviceService.device(chargeOrder.deviceId, ['strategyId', 'simCardNo'])
        if(!device || !device.simCardNo) return false

        const strategy = await this.chargeStrategyService.strategy(device.strategyId, ['strategyType', 'unitPrice', 'settleUnit'])
        //策略如果改变了，则不支持增加充电

        if(!strategy ||
            strategy.strategyType != chargeOrder.strategyType || strategy.unitPrice != chargeOrder.unitPrice || strategy.settleUnit != chargeOrder.settleUnit    
        ) return errorReturn('该订单不支持增加充电')

        const time = getTime()

        let order:{[key in keyof AddChargeOrder]? : AddChargeOrder[key]} = {
            userId : userId,
            relationOrderId : chargeOrderId,
            feeAmount : '0',
            feeRewardAmount : '0',
            chargeAmount : String(amount),
            createTime : time,
            status : 1,
        }

        let isSuccess = false

        const queryRunner = await this.chargeService.startTransaction()

        try {
            let wallet = await queryRunner.manager.findOne<UserWallet>(UserWallet, {
                where : {userId : userId},
                select : ['amount', 'rewardAmount'],
                lock : {
                    mode : 'pessimistic_read'
                }
            })

            let rechargeBalance = Number(wallet.amount) //充值的余额
            let rewardBalance = Number(wallet.rewardAmount) //赠送的余额

            if((rechargeBalance + rewardBalance) < amount) { //余额不足
                return errorReturn('余额不足')
            }

            let { walletLog, walletUpdate} = this.chargeService.walletUpdate(amount, rechargeBalance, rewardBalance, order, wallet)
            walletLog.type = 4

            {
                //更新钱包
                const { affected } = await queryRunner.manager.update<UserWallet>(UserWallet, { userId : order.userId }, walletUpdate)
                if(affected < 1) {
                    return errorReturn('更新钱包失败')
                }
            }

            let orderId = 0

            {
                //加入订单
                const { raw } = await queryRunner.manager.insert<AddChargeOrder>(AddChargeOrder, order)
                if(!raw || raw.affectedRows < 1) {
                    return errorReturn('加入定点失败')
                }
                walletLog.relationId = orderId = raw.insertId
            }

            {
                //加入钱包明细
                walletLog.createTime = order.createTime
                const { raw } = await queryRunner.manager.insert<UserWalletLog>(UserWalletLog, walletLog)
                if(!raw || raw.affectedRows < 1) {
                    return errorReturn('加入钱包明细失败')
                }
            }

            this.redis.store.set(String(chargeOrderId), String(orderId), {EX : 80})
            this.redis.store.set(this.addChargeResKey + orderId, String(userId), {NX : true, EX : 70})
            this.rabbitmqService.publish(this.chargeResExchange, this.chargeResQueue, {type : 2, orderId}, {expiration : 60000, timestamp : time})

            this.mqttService.publish(this.chargeTopics + '/' + device.simCardNo, {
                cmdType : 28,
                debug : 0,
                chargeAction : 1,
                outletNum:chargeOrder.portNo,
                orderMsgId : chargeOrderId,
                consumeMode:chargeOrder.strategyType,
                payMoney : amount * 100,
                totalTimOrVol : chargeOrder.strategyType == 2 ? amount * Number(chargeOrder.unitPrice) : amount * Number(chargeOrder.unitPrice) * 60,
                unlockPwd : '',
            })

            
            await queryRunner.commitTransaction()

            isSuccess = true

            return successReturn(orderId)

        } catch (error) {
            console.log(error)
        } finally {
            if(!isSuccess) {
                await queryRunner.rollbackTransaction()
            }

            queryRunner.release()
        }

    }

    @Post('stop')
    async stopCharge(@Body('orderId') orderId:number, @Req() {userId}) {
        orderId = Number(orderId)
        if(!isNumber(orderId) || orderId < 1) return null
        const order = await this.chargeOrderService.order(orderId, ['status', 'deviceId', 'portNo', 'userId'])
        if(!order || order.userId != userId || order.status != 1) return null

        const device = await this.deviceService.device(order.deviceId, ['simCardNo'])
        if(!device) return null

        this.mqttService.publish(this.chargeTopics + '/' + device.simCardNo, {
            cmdType : 28,
            debug : 0,
            chargeAction : 2,
            outletNum:order.portNo,
            orderMsgId : orderId,
        })

        return successReturn()
    }


    @Get('status/:orderId')
    async startChargeStatus(@Param('orderId') orderId:number) {
        orderId = Number(orderId)
        if(!isNumber(orderId) || orderId < 1) return null

        const rs = await this.redis.store.get(this.starChargeResKey + orderId)
        let status = 'pending'
        if(rs == null || rs == '2') {
            status = 'fail'
        }else if(rs == '1') {
            status = 'success'
        }
        
        return successReturn(status)
    }

    @Get('addStatus/:orderId')
    async addChargeStatus(@Param('orderId') orderId:number) {
        orderId = Number(orderId)
        if(!isNumber(orderId) || orderId < 1) return null

        const rs = await this.redis.store.get(this.addChargeResKey + orderId)
        let status = 'pending'
        if(rs == null || rs == '2') {
            status = 'fail'
        }else if(rs == '1') {
            status = 'success'
        }
        
        return successReturn(status)
    }

    @Get('stopStatus/:orderId')
    async stopChargeStatus(@Param('orderId') orderId:number) {
        orderId = Number(orderId)
        if(!isNumber(orderId) || orderId < 1) return null

        const rs = await this.redis.store.get(this.stopChargeResKey + orderId)
        let status = 'pending'
        if(rs == null || rs == '2') {
            status = 'fail'
        }else if(rs == '1') {
            status = 'success'
        }
        
        return successReturn(status)
    }
}
